//TODO - Reduzir o acoplamento entre Board e BoardPannel

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package game_logic;

import exceptions.InvalidMoveException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Classe que representa o tabuleiro do jogo.
 *
 * Essa classe implementa as estrura de dados do caminho do tabuleiro, métodos
 * para mover as peças.
 * @author Danilo
 */
public class Board {

    private List<SquareR> path;
    private Map<Color, List<SquareR>> finalPath;
    private Dice dice;
    private static final Color[] colors = {Color.GREEN, Color.RED, Color.BLUE, Color.YELLOW};
    private static final int[] initialPositions = {1, 14, 27, 40};
    private Map<Color, List<Pawn>> base;
    private Map<String, Pawn> pawns;
    private int diceValue;

    /**
     * Construtor da classe.
     */
    public Board() {
        initializeBoard();
        initializeFinalPath();
        initializeBases();
        initializePawns();
    }

    /**
     * Retorna uma representação em string do tabuleiro.
     *
     * Imprime cada casa com o seguinte padrão: *
     *  indica que tem peão na casa
     * , S indica que é uma casa inicial de alguma cor
     * , F indica que é uma casa final de alguma cor
     * , B,G,Y,R diz a cor da casa, se for inicial ou final
     * 
     * @return Uma string representando o tabuleiro.
     */
    @Override
    public String toString() {

        StringBuffer buff = new StringBuffer();

        printPath(buff);

        System.out.println();

        printFinalPath(buff);

        printBases(buff);

        return buff.toString();
    }

    private void printPath(StringBuffer buff) {
        for (SquareR s : path) {
            //System.out.print(s);
            buff.append(s.toString());
        }
    }

    private void printFinalPath(StringBuffer buff) {
        for (Color c : finalPath.keySet()) {
            List<SquareR> list = finalPath.get(c);
            buff.append('\n');
            for (SquareR s : list) {
                //System.out.print(s);
                buff.append(s.toString());
            }
        }
    }

    private void printBases(StringBuffer buff) {
        buff.append("\n\nBase vermelha: ");
        for (Pawn p : base.get(Color.RED)) {
            buff.append(" * ");
        }
        buff.append("\nBase azul: ");
        for (Pawn p : base.get(Color.BLUE)) {
            buff.append(" * ");
        }
        buff.append("\nBase amarela: ");
        for (Pawn p : base.get(Color.YELLOW)) {
            buff.append(" * ");
        }
        buff.append("\nBase verde: ");
        for (Pawn p : base.get(Color.GREEN)) {
            buff.append(" * ");
        }
    }

    /**
     * Move um peão um certo número de casas.
     *
     * Se a casa de destino for ocupada:
     * caso exista um peão de outra cor, o peão que estava lá volta pra base;
     * se existir um peão da mesma cor, o movimento é inválido
     *
     * @param pawn O peão a ser movido
     * @param numberSteps O número de casas a ser caminhado.
     * @return True, se o movimento for bem sucedido, False, caso contrário.
     */
    private boolean move(Pawn pawn, int numberSteps) throws InvalidMoveException {
        int finalPos = pawn.getPosition() + numberSteps;

        // Parece com aquelas filas em arrays que a gente via em 
        // estrutura de dados 1, hehehehe
        if (finalPos > 51) {
            finalPos = finalPos - 52;
        }

        // aqui trata a lógica de entrar na reta final
        if (pawn.getCounterSteps() + numberSteps > 50) {
            pawn.setFinalPath(true);
            pawn.setPosition((pawn.getCounterSteps() + numberSteps) - 51);

            path.get(pawn.getPosition()).setPawn(null);
            finalPath.get(pawn.getColor()).get(pawn.getPosition()).setPawn(pawn);

            return true;
        }

        SquareR sq = path.get(finalPos);
        if (sq.getPawn() != null) {
            if (sq.getPawn().getColor() != pawn.getColor()) {
                // Manda o peão que estava lá pra a casa do caralho!!!
                Pawn p = sq.getPawn();
                base.get(p.getColor()).add(p);
                p.clearSteps();
            } else {
                throw new InvalidMoveException("Casa ocupada por peão de mesma cor");
            }
        }

        putPawn(pawn, finalPos);
        return true;

    }

    private void initializeFinalPath() {
        finalPath = new HashMap<Color, List<SquareR>>();

        for (int i = 0; i < 4; i++) {
            List<SquareR> list = new ArrayList<SquareR>();

            for (int j = 0; j < 5; j++) {
                SquareR s = new SquareR(j);
                s.setColor(colors[i]);
                list.add(s);
            }

            finalPath.put(colors[i], list);
        }
    }

    private void initializeBoard() {
        path = new ArrayList<SquareR>();

        for (int i = 0; i <= 51; i++) {
            SquareR sq = new SquareR(i);
            path.add(sq);
        }

        int color = 0;
        int pos = 1;

        while (pos < 51) {
            path.get(pos).setColor(colors[color]);
            path.get(pos).setIsStart(true);

            color++;
            pos += 13;
        }

    }

    /**
     * Coloca um peão em uma determinada casa.
     * @param p O peão a ser colocado.
     * @param i O número da casa onde vai ser posto o peão.
     */
    private void putPawn(Pawn p, int i) {
        if (p.getPosition() != -1) {
            path.get(p.getPosition()).setPawn(null);
        }
        path.get(i).setPawn(p);
        p.setPosition(i);
    }

    private void initializePawns() {
        pawns = new HashMap<String, Pawn>();

        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                Color c = colors[i];
                Pawn p = new Pawn(this, c, j);
                pawns.put("[" + i + "," + j + "]", p);
                base.get(c).add(p);
            }
        }
    }

    private void initializeBases() {
        base = new HashMap<Color, List<Pawn>>();
        for (int i = 0; i < 4; i++) {
            base.put(colors[i], new ArrayList<Pawn>());
        }
    }

    public void executeCommand(Command cmm) throws InvalidMoveException {
        if (cmm instanceof MoveCommand) {
            MoveCommand moveCommand = (MoveCommand) cmm;
            executeCommand(moveCommand);
        } else if (cmm instanceof RemovePawnCommand) {
            RemovePawnCommand removeCommand = (RemovePawnCommand) cmm;
            executeCommand(removeCommand);
        }
    }

    private void executeCommand(MoveCommand move) throws InvalidMoveException {
        move(move.getPawn(), move.getNumberOfSteps());
    }

    private void executeCommand(RemovePawnCommand remove) throws InvalidMoveException {
        Color c = remove.getColor();

        if (base.get(c).isEmpty()
                || path.get(initialPositions[c.getNumber()]).isOccupied()) {
            throw new InvalidMoveException("Movimento Ilegal");
        } else {
            Pawn p = base.get(c).remove(0);
            putPawn(p, initialPositions[c.getNumber()]);
        }
    }

    public static void main(String[] args) {
        try {
            Board b = new Board();

            RemovePawnCommand rm = new RemovePawnCommand(Color.RED);

            MoveCommand move = new MoveCommand(b.pawns.get("[" + Color.RED.getNumber() + "," + 0 + "]"), 6);

            b.executeCommand(rm);
            b.executeCommand(move);

            System.out.println(b);
        } catch (InvalidMoveException ex) {
            ex.printStackTrace();
        }
    }

    public Pawn getPawn(Color c, int id) {
        return pawns.get("[" + c.getNumber() + "," + id + "]");
    }

    public int rollDice(){
        diceValue = dice.roll();
        return diceValue;
    }

    public int getDiceValue(){
        return diceValue;
    }

    public List<Square> getPath() {
        List<Square> list = new ArrayList<Square>();

        for( SquareR sr: path ){
            SquareProxy sp = new SquareProxy(sr);
            list.add(sp);
        }

        return list;
    }
}

